/**
 * Anderground project - Subway maps processor application.
 * Copyright (C) 2008-2009 Eldar Abusalimov, Ilya Shurigyn
 * 
 * This file is part of Anderground.
 * 
 * Anderground is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * Anderground is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Anderground.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.anderground.core.model;

import java.util.List;

import org.anderground.core.util.Coord;
import org.anderground.core.util.Point;
import org.anderground.core.util.SparseArray;
import org.anderground.core.util.Time;

/**
 * The wrapper around {@link ModelData.Builder} that performs bind-by-id
 * resolving between elements of {@link Station}, {@link Line}, {@link Tunnel}
 * and {@link Transfer} before actually adding them to the {@link ModelData}.
 * 
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 */
public class IdBindingModelBuilder {

	/** id-to-station mapping */
	private SparseArray<Station> stationsMap = new SparseArray<Station>();

	/** id-to-line mapping */
	private SparseArray<Line> linesMap = new SparseArray<Line>();

	/** the target modelBuilder to wrap */
	private ModelData.Builder modelBuilder;

	/**
	 * The default constructor.
	 */
	public IdBindingModelBuilder() {
		modelBuilder = new ModelData.Builder();
	}

	/**
	 * Forces the {@link ModelData} instance internal building and returns the
	 * instance.
	 * 
	 * @return the ModelData instance being built
	 * @throws ModelException
	 */
	public ModelData build() throws ModelException {
		modelBuilder.forceRebuild();
		return modelBuilder.getModelData();
	}

	/**
	 * Adds the {@link Station} to the model also associating it with the
	 * specified id on which the station will be accessible at addition of
	 * {@link Transition}s.
	 * 
	 * @param id
	 *            the key on which the station will be accessible at addition of
	 *            Transitions
	 * @param station
	 *            the station to add
	 * @throws ModelException
	 *             if underlying {@link ModelData.Builder} fails to add the
	 *             station
	 */
	public void addStation(int id, Station station) throws ModelException {
		stationsMap.put(id, station);
		modelBuilder.add(station);
	}

	/**
	 * Adds the {@link Line} to the model also associating it with the specified
	 * id on which the line will be accessible at addition of {@link Tunnel}s.
	 * 
	 * @param id
	 *            the key on which the line will be accessible at addition of
	 *            Tunnels
	 * @param line
	 *            the line to add
	 */
	public void addLine(int id, Line line) {
		linesMap.put(id, line);
	}

	/**
	 * Adds the specified {@link Description} to the model.
	 * 
	 * @param description
	 *            the description to add
	 */
	public void setDescription(Description description) {
		modelBuilder.setDescription(description);
	}

	/**
	 * Adds the specified {@link City} to the model.
	 * 
	 * @param city
	 *            the city to add
	 */
	public void setCity(City city) {
		modelBuilder.setCity(city);
	}

	/**
	 * Creates a new {@link Station} and adds it to the model also associating
	 * it with the specified id on which the station will be accessible at
	 * addition of {@link Transition}s.
	 * 
	 * @param id
	 *            the key on which the station will be accessible at addition of
	 *            transitions
	 * @param name
	 *            station name
	 * @param position
	 *            position on phone screen
	 * @param location
	 *            location on the world map
	 * @param open
	 *            open time
	 * @param close
	 *            close time
	 * @param labelX
	 *            - label's offset from the station center in screen pixels x
	 *            value
	 * @param labelY
	 *            - label's offset from the station center in screen pixels y
	 *            value
	 * @param labelAngle
	 *            - angle to rotate the label
	 * @param labelVisible
	 *            - label visibility
	 * @return the new station created
	 */
	public Station addStation(int id, String name, Coord position,
			Coord location, Time open, Time close, int labelX, int labelY,
			int labelAngle, boolean labelVisible) {
		Station.Label label = new Station.Label(labelAngle, new Point(labelX,
				labelY), labelVisible);
		Station station = new Station(name, position, location, open, close,
				label);
		try {
			addStation(id, station);
		} catch (ModelException e) {
			// bypass - unreachable
			assert false;
		}
		return station;
	}

	/**
	 * Creates a new {@link Line} and adds it to the model also associating it
	 * with the specified id on which the line will be accessible at addition of
	 * {@link Tunnel}s.
	 * 
	 * @param id
	 *            the key
	 * @param name
	 *            the line name
	 * @param color
	 *            the color to draw the line with
	 * @param trainDelay
	 *            average train delay in seconds
	 * @return the new line created
	 */
	public Line addLine(int id, String name, int color, int trainDelay) {
		Line line = new Line(name, color, trainDelay);
		addLine(id, line);
		return line;
	}

	/**
	 * Creates a new {@link Tunnel} and adds it to the model.
	 * <p>
	 * Please note, that the {@link Station}s with <i>id</i> passed in {@code
	 * from} and {@code to} arguments should already be added. This concerns
	 * including {@link Line}s (the {@code line} argument). Otherwise, a
	 * {@link IdBindingModelException} will be thrown.
	 * 
	 * @param from
	 *            the <i>id</i> of the station that new transition leads from
	 *            (this station should already be added)
	 * @param to
	 *            the <i>id</i> of the station that new transition leads to
	 *            (this station should already be added)
	 * @param time
	 *            the time is seconds
	 * @param line
	 *            the <i>id</i> of the line that this tunnel belongs to (the
	 *            line should be already added)
	 * @param smooth
	 *            the tunnel smoothness
	 * @throws IdBindingModelException
	 *             if there are no stations with such id as you have presented
	 *             in <tt>from</tt> and <tt>to</tt> arguments, or there has no
	 *             line been added with id presented in <tt>line</tt> argument
	 * @throws ModelException
	 *             if underlying {@link ModelData.Builder} fails to add the
	 *             tunnel
	 * @see IdBindingModelBuilder#addStation(int, Station)
	 * @see IdBindingModelBuilder#addStation(int, String, Coord, Coord, Time,
	 *      Time, int, int, int, boolean)
	 * @see IdBindingModelBuilder#addLine(int, Line)
	 * @see IdBindingModelBuilder#addLine(int, String, int, int)
	 */
	public void addTunnel(int from, int to, int time, int line, boolean smooth)
			throws IdBindingModelException, ModelException {
		Station fromStation = stationsMap.get(from);
		if (fromStation == null) {
			throw new IdBindingModelException("No station with id " + from);
		}
		Station toStation = stationsMap.get(to);
		if (toStation == null) {
			throw new IdBindingModelException("No station with id " + to);
		}
		Line theLine = linesMap.get(line);
		if (theLine == null) {
			throw new IdBindingModelException("No line with id " + line);
		}
		modelBuilder.add(new Tunnel(fromStation, toStation, time, theLine,
				smooth));
	}

	/**
	 * Creates a new {@link Transfer} and adds it to the model.
	 * <p>
	 * Please note, that the stations with <i>id</i> passed in <tt>from</tt> and
	 * <tt>to</tt> arguments should already be added with
	 * {@link IdBindingModelBuilder#addStation(int, String, Coord, Coord, Time, Time, int, int, int, boolean)}
	 * Otherwise, a {@link IdBindingModelException} will be thrown.
	 * 
	 * @param from
	 *            the id of the station that new transition leads from (this
	 *            station should already be added)
	 * @param to
	 *            the id of the station that new transition leads to (this
	 *            station should already be added)
	 * @param time
	 *            the time is seconds
	 * @throws IdBindingModelException
	 *             if there are no stations with such id as you have presented
	 *             in <tt>from</tt> and <tt>to</tt> arguments
	 * @throws ModelException
	 *             if underlying {@link ModelData.Builder} fails to add the
	 *             transfer
	 * @see IdBindingModelBuilder#addStation(int, Station)
	 * @see IdBindingModelBuilder#addStation(int, String, Coord, Coord, Time,
	 *      Time, int, int, int, boolean)
	 */
	public void addTransfer(int to, int from, int time)
			throws IdBindingModelException, ModelException {
		Station fromStation = stationsMap.get(from);
		if (fromStation == null) {
			throw new IdBindingModelException("No station with id " + from);
		}
		Station toStation = stationsMap.get(to);
		if (toStation == null) {
			throw new IdBindingModelException("No station with id " + to);
		}
		modelBuilder.add(new Transfer(fromStation, toStation, time));
	}

	/**
	 * Creates a new {@link Description} and adds it to the model.
	 * 
	 * @param authors
	 *            the authors of the map
	 * @param comment
	 *            the comment to set
	 */
	public void setDescription(List<String> authors, String comment) {
		setDescription(new Description(authors, comment));
	}

	/**
	 * Creates a new {@link City} and adds it to the model.
	 * 
	 * @param name
	 *            the name of the city
	 * @param aliases
	 * @param countryCode
	 * @param countryName
	 * @param location
	 */
	public void setCity(String name, List<String> aliases, String countryCode,
			String countryName, Coord location) {
		setCity(new City(name, aliases, new Country(countryCode, countryName),
				location));
	}

}
